package com.bifrost.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bifrost.constant.BonusPointsSource;
import com.bifrost.constant.BonusPointsUpdatedDetail;
import com.bifrost.constant.BonusPointsUpdatedType;
import com.bifrost.constant.OrderItemStatus;
import com.bifrost.entity.*;
import com.bifrost.exception.BonusPointsNotEnoughException;
import com.bifrost.exception.ExternalOrderProcessException;
import com.bifrost.exception.KnownRuntimeException;
import com.bifrost.exception.OriginOrderNotFundException;
import com.bifrost.model.ExternalOrder;
import com.bifrost.model.ExternalOrderItem;
import com.bifrost.model.vo.BonusPointsHistoryVo;
import com.bifrost.model.vo.PageVO;
import com.bifrost.repository.*;
import com.bifrost.service.BonusPointsService;
import com.bifrost.service.ProductSkuService;
import com.bifrost.service.StoreService;
import com.bifrost.uid.impl.DefaultUidGenerator;
import com.bifrost.util.BigDecimalUtil;
import com.bifrost.util.DateUtils;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.runtime.directive.Foreach;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author rangf
 * @date 2018/4/9
 */
@Service
@Slf4j
public class BonusPointsServiceImpl implements BonusPointsService {

    @Autowired
    CustomerRepository customerRepository;
    @Autowired
    BonusPointsHistoryRepository bonusPointsHistoryRepository;
    @Autowired
    ProductSkuService skuService;
    @Autowired
    ProductSkuRepository skuRepository;
    @Autowired
    BonusPointsCategoryRepository bonusPointsCategoryRepository;
    @Autowired
    BonusPointsCustomConfigRepository bonusPointsCustomConfigRepository;
    @Autowired
    BonusPointsConfigRepository bonusPointsConfigRepository;
    @Autowired
    StoreService storeService;
    @Autowired
    CustomerCardHistoryRepository customerCardHistoryRepository;
    @Autowired
    DefaultUidGenerator defaultUidGenerator;

    @Autowired
    OrderRepository orderRepository;
    @Autowired
    OrderReturnRepository returnRepository;
    @Autowired
    BonusExceptionOrderRepository exceptionOrderRepository;
    /**
     * 注该map线程不安全，仅做缓存使用
     */
    private final Map<String, BonusPointsCategory> pathCacheMap = new HashMap();

    /**
     * 扣减会员积分
     *
     * @param customer
     * @param orderNumber
     * @param originNumber
     * @param decrementValue
     * @param source
     * @param detail
     * @param remark
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long decrementBonusPoints(Customer customer, String orderNumber, String originNumber, BigInteger decrementValue, BonusPointsSource source, BonusPointsUpdatedDetail detail, String remark) throws BonusPointsNotEnoughException {
        return decrementBonusPoints(customer, orderNumber, originNumber, decrementValue, source, detail, remark, null);
    }

    /**
     * 扣减会员积分并保存原始数据
     *
     * @param customer
     * @param orderNumber
     * @param originNumber
     * @param decrementValue
     * @param source
     * @param detail
     * @param remark
     * @param data
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long decrementBonusPoints(Customer customer, String orderNumber, String originNumber, BigInteger decrementValue, BonusPointsSource source, BonusPointsUpdatedDetail detail, String remark, String data) throws BonusPointsNotEnoughException {
        if (decrementValue.compareTo(BigInteger.ZERO) < 0) {
            throw new BonusPointsNotEnoughException("扣减积分值必须大于等于0");
        }
        BigInteger bonusPoints = customer.getBonusPoints();
        if (bonusPoints.compareTo(decrementValue) < 0) {
            throw new BonusPointsNotEnoughException("会员积分余额不足");
        }
        BonusPointsHistory history = saveBonusPointsHistory(customer.getMerchantStore(), customer.getLoyaltyCard(), orderNumber, originNumber, decrementValue, BonusPointsUpdatedType.DECREMENT, detail, source, remark, data);
        int updated = customerRepository.decrementBonusPoints(customer.getCustomerId(), decrementValue);
        if (updated != 1) {
            throw new BonusPointsNotEnoughException("扣减会员积分失败");
        } else {
            //重新查询一次
            //customer = customerRepository.findOne(customer.getCustomerId());
            history.setAfterValue(customer.getBonusPoints().subtract(decrementValue));
            bonusPointsHistoryRepository.save(history);
            return history.getBonusPointsHistory();
        }
    }

    /**
     * 增加会员积分
     *
     * @param customer
     * @param orderNumber
     * @param incrementValue
     * @param source
     * @param detail
     * @param remark
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long incrementBonusPoints(Customer customer, String orderNumber, BigInteger incrementValue, BonusPointsSource source, BonusPointsUpdatedDetail detail, String remark) {
        return incrementBonusPoints(customer, orderNumber, incrementValue, source, detail, remark, null);
    }

    /**
     * 增加会员积分并保存原始数据
     *
     * @param customer
     * @param orderNumber
     * @param incrementValue
     * @param source
     * @param detail
     * @param remark
     * @param data
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long incrementBonusPoints(Customer customer, String orderNumber, BigInteger incrementValue, BonusPointsSource source, BonusPointsUpdatedDetail detail, String remark, String data) {
        if (incrementValue.compareTo(BigInteger.ZERO) < 0) {
            throw new RuntimeException("积分增加值必须大于0");
        }
        BonusPointsHistory history = saveBonusPointsHistory(customer.getMerchantStore(), customer.getLoyaltyCard(), orderNumber, null, incrementValue, BonusPointsUpdatedType.INCREMENT, detail, source, remark, data);
        int updated = customerRepository.incrementBonusPoints(customer.getCustomerId(), incrementValue);
        if (updated != 1) {
            throw new RuntimeException("增加会员积分失败");
        } else {
            //重新查询一次
            //customer = customerRepository.findOne(customer.getCustomerId());
            history.setAfterValue(customer.getBonusPoints().add(incrementValue));
            bonusPointsHistoryRepository.save(history);
            return history.getBonusPointsHistory();
        }
    }

    /**
     * 记录积分日志
     *
     * @param customer
     * @param number
     * @param originNumber
     * @param changeValue
     * @param changeType
     * @param source
     * @param remark
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public BonusPointsHistory saveBonusPointsHistory(Customer customer, String number, String originNumber, BigInteger changeValue, BonusPointsUpdatedType changeType, BonusPointsUpdatedDetail detail, BonusPointsSource source, String remark) {
        return saveBonusPointsHistory(customer.getMerchantStore(), customer.getLoyaltyCard(), number, originNumber, changeValue, changeType, detail, source, remark, null);
    }

    /**
     * 记录积分日志并保存原始数据
     *
     * @param store
     * @param cardNumber
     * @param orderNumber
     * @param originNumber
     * @param changeValue
     * @param changeType
     * @param source
     * @param remark
     * @param data
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public BonusPointsHistory saveBonusPointsHistory(MerchantStore store, String cardNumber, String orderNumber, String originNumber, BigInteger changeValue, BonusPointsUpdatedType changeType, BonusPointsUpdatedDetail detail, BonusPointsSource source, String remark, String data) {
        BonusPointsHistory history = new BonusPointsHistory();
        //MerchantStore store = customer.getMerchantStore();
        //history.setCustomer(customer);
        Customer customer = customerRepository.findFirstByLoyaltyCard(cardNumber);
        history.setCustomerId(customer.getCustomerId());
        history.setCustomerLastName(customer.getCustomerLastname());
        history.setCustomerPhone(customer.getCustomerPhone());
        history.setNumber(orderNumber);
        history.setCardNumber(cardNumber);
        history.setMerchantId(store.getMerchantId());
        history.setStoreCode(store.getStoreCode());
        history.setOriginNumber(originNumber);
        history.setUpdatedValue(changeValue);
        history.setUpdatedType(changeType);
        history.setUpdatedTypeDetail(detail);
        history.setSource(source);
        history.setRemark(remark);
        history.setUpdatedTimestamp(new Date());
        history.setData(data);
        history.setTransactionId(defaultUidGenerator.getUID());
        return bonusPointsHistoryRepository.save(history);
    }

    /**
     * 获取用户积分历史记录
     *
     * @param customer
     * @return
     */
    @Override
    public List<BonusPointsHistory> getBonusPointsHistories(Customer customer) {
        if (StringUtils.isNotBlank(customer.getLoyaltyCard())) {
            return bonusPointsHistoryRepository.findAllByMerchantIdAndCardNumber(customer.getMerchantStore().getMerchantId(), customer.getLoyaltyCard());
        }
        return null;
    }

    /**
     * 返回指定会员、指定sku的积分率
     * <p>
     * 取数顺序：先获取sku上设置的积分率为null，则取自定积分规则，ratio值为0 则认为不积分
     * </p>
     * <p>
     * ratio->积分率
     * times->倍率
     * </p>
     * 注：促销积分率若为null默认为0即促销默认不积分
     *
     * @param skuId
     * @param customer
     * @param promoApplied
     * @return
     */
    @Override
    public Map getBonusPointsRatio(Long skuId, Customer customer, boolean promoApplied) {
        Map map = new HashMap<String, Integer>(2);
        Integer bonusPointsRate = null;
        BigDecimal times = BigDecimal.ONE;
        ProductSku productSku = skuService.getSkuById(skuId);
        String externalCategory = productSku.getProduct().getExternalCcategory();
        if (productSku.getBonusPointsRate() != null) {
            bonusPointsRate = productSku.getBonusPointsRate();
        } else if (StringUtils.isNotBlank(externalCategory)) {
            return getCustomConfigMap(customer, externalCategory, promoApplied);
        }
        map.put("ratio", bonusPointsRate);
        map.put("times", times);
        return map;
    }

    /**
     * 按照外部编码和类别编码计算积分比率
     *
     * @param externalId
     * @param categoryCode
     * @param customer
     * @param promoApplied
     * @return
     */
    @Override
    public Map getBonusPointsRatio(String externalId, String categoryCode, Customer customer, boolean promoApplied) {
        Map map = new HashMap(2);
        Integer bonusPointsRate = null;
        BigDecimal times = BigDecimal.ONE;
        map.put("ratio", bonusPointsRate);
        map.put("times", times);

        ProductSku productSku = skuRepository.findFirstByExternalIdAndMerchantId(externalId , customer.getMerchantStore().getMerchantId());
        if (productSku != null) {
            if (productSku.getBonusPointsRate() != null) {
                bonusPointsRate = productSku.getBonusPointsRate();
                map.put("ratio", bonusPointsRate);
                return map;
            }
        }
        return getCustomConfigMap(customer, categoryCode, promoApplied);
    }

    private void findPathBacktrack(BonusPointsCategory category, List<String> path) {
        Assert.notNull(category, "品类不能为空");
        if (category != null) {
            path.add(category.getCode());
            String code = category.getParentCode();
            if (StringUtils.isNotBlank(code)) {
                if (pathCacheMap.containsKey(code)) {
                    findPathBacktrack(pathCacheMap.get(code), path);
                } else {
                    synchronized (pathCacheMap) {
                        if (!pathCacheMap.containsKey(code)) {
                            BonusPointsCategory bonusPointsCategory = bonusPointsCategoryRepository.findFirstByCode(code);
                            pathCacheMap.put(code, bonusPointsCategory);
                            findPathBacktrack(bonusPointsCategory, path);
                        } else {
                            findPathBacktrack(pathCacheMap.get(code), path);
                        }
                    }
                }
            }
        }
    }

    private Map getCustomConfigMap(MerchantStore store, String externalCategory, boolean promoApplied) {
        Map map = new HashMap(2);
        Integer bonusPointsRate = null;
        BigDecimal times = BigDecimal.ONE;
        ArrayList<String> path = new ArrayList();
        //查询自定义配置
        BonusPointsCategory bonusPointsCategory = bonusPointsCategoryRepository.findFirstByCode(externalCategory);
        findPathBacktrack(bonusPointsCategory, path);
        if (CollectionUtils.isNotEmpty(path)) {
            List<BonusPointsCustomConfig> customConfigs = bonusPointsCustomConfigRepository.findAllActiveConfig(path, store.getMerchantId(), new Date());
            if (CollectionUtils.isNotEmpty(customConfigs)) {
                block:
                for (String code : path) {
                    for (BonusPointsCustomConfig config : customConfigs) {
                        if (code.equals(config.getCategoryCode())) {
                            bonusPointsRate = config.getBonusPointsRatio();
                            if (bonusPointsRate == null) {
                                //未设置自定义积分率取倍率
                                times = config.getBonusPointsTimes();
                            }
                            break block;
                        }
                    }
                }
            }
            //查找默认配置
            if (bonusPointsRate == null) {
                List<BonusPointsConfig> configs = bonusPointsConfigRepository.findAllConfig(path, store.getMerchantId());
                if (CollectionUtils.isNotEmpty(configs)) {
                    block:
                    for (String code : path) {
                        for (BonusPointsConfig config : configs) {
                            if (code.equals(config.getCategoryCode())) {
                                if (promoApplied) {
                                    bonusPointsRate = config.getPromotionPointsRatio() == null ? config.getBonusPointsRatio() : config.getPromotionPointsRatio();
                                } else {
                                    bonusPointsRate = config.getBonusPointsRatio();
                                }
                                break block;
                            }
                        }
                    }
                }
            }
        }
        map.put("ratio", bonusPointsRate == null ? 0 : bonusPointsRate);
        map.put("times", times);
        return map;
    }

    /**
     * 查询自定义积分规则配置
     *
     * @param customer
     * @param externalCategory
     * @return
     */
    private Map getCustomConfigMap(Customer customer, String externalCategory, boolean promoApplied) {
        Assert.notNull(customer, "会员不能为空");
        return getCustomConfigMap(customer.getMerchantStore(), externalCategory, promoApplied);
    }


    /**
     * 处理外部订单
     * <p>
     * 退货积分不足够将抛出BonusPointsNotEnoughException<br>
     * 退货找不到原单相关记录将抛出OriginOrderNotFundException
     * </p>
     *
     * @param externalOrder
     * @return
     * @throws BonusPointsNotEnoughException
     * @throws OriginOrderNotFundException
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long processExternalOrder(ExternalOrder externalOrder) throws BonusPointsNotEnoughException, OriginOrderNotFundException {
        Assert.notNull(externalOrder, "订单不能为空");
        String storeCode = externalOrder.getStoreCode();
        String orderNumber = externalOrder.getOrderNumber();
        String cardNumber = externalOrder.getCardNumber();
        Assert.hasText(storeCode, "门店编号不能为空");
        Assert.hasText(orderNumber, "订单编号不能为空");
        Assert.hasText(cardNumber, "会员卡号不能为空");
        MerchantStore store = storeService.getMerchantStoreByExternalId(storeCode);
        Assert.notNull(store, "指定编码的门店在系统未注册");

        Customer customer = customerRepository.findFirstByMerchantStoreAndLoyaltyCard(store, cardNumber);
        /**
         * 退货时需校验原单是否存在不存在抛出异常
         * */
        if (ExternalOrder.Types.REFUND.toString().equalsIgnoreCase(externalOrder.getType())) {
            String originNumber = externalOrder.getOriginNumber();
            Assert.hasText(cardNumber, "原订单编号不能为空");
            //先查原单是否有记录
            BonusPointsHistory originHistory = findProcessedHistory(originNumber, cardNumber, BonusPointsUpdatedType.INCREMENT);
            if (originHistory != null) {
                BonusPointsHistory history = findProcessedHistory(orderNumber, cardNumber, BonusPointsUpdatedType.DECREMENT);
                if (history == null) {
                    if (customer != null) {
                        //为当前绑定会员
                        calculateBonusPoints(customer, externalOrder);
                        Long transactionId = decrementBonusPoints(customer,
                                externalOrder.getOrderNumber(),
                                externalOrder.getOriginNumber(),
                                BigInteger.valueOf(externalOrder.getTotalBonusPoints()),
                                BonusPointsSource.OFFLINE_STORE,
                                BonusPointsUpdatedDetail.ORDER_REFUND,
                                String.format("线下退货订单:%s,扣减赠送积分%d",
                                        externalOrder.getOrderNumber(),
                                        externalOrder.getTotalBonusPoints()),
                                JSONObject.toJSONString(externalOrder));
                        return transactionId;
                    } else {
                        //当前会员未绑定该会员卡,查询绑卡记录
                        CustomerCardHistory cardHistory = customerCardHistoryRepository.findFirstByMerchantIdAndCardNumber(store.getMerchantId(), cardNumber);
                        if (cardHistory == null) {
                            throw new OriginOrderNotFundException(String.format("未找到会员:%s的相关原单记录信息：%s", cardNumber, originNumber));
                        }
                        calculateBonusPoints(null, externalOrder);
                        int updated = customerCardHistoryRepository.decrementHistoryBonusPoints(cardHistory.getId(), BigInteger.valueOf(externalOrder.getTotalBonusPoints()));
                        if (updated == 0) {
                            throw new BonusPointsNotEnoughException(String.format("会员%s积分余额不足：%d", cardNumber, cardHistory.getBonusPonits()));
                        } else {
                            BonusPointsHistory bonusPointsHistory = saveBonusPointsHistory(store,
                                    cardNumber,
                                    externalOrder.getOrderNumber(),
                                    externalOrder.getOriginNumber(),
                                    BigInteger.valueOf(externalOrder.getTotalBonusPoints()),
                                    BonusPointsUpdatedType.DECREMENT,
                                    BonusPointsUpdatedDetail.ORDER_REFUND,
                                    BonusPointsSource.OFFLINE_STORE,
                                    String.format("线下退货订单:%s,扣减赠送积分%d", externalOrder.getOrderNumber(), externalOrder.getTotalBonusPoints()),
                                    JSONObject.toJSONString(externalOrder));
                            return bonusPointsHistory.getTransactionId();
                        }
                    }
                } else {
                    return history.getTransactionId();
                }
            } else {
                throw new OriginOrderNotFundException(String.format("未找到会员:%s的相关原单记录信息：%s", cardNumber, originNumber));
            }
        } else if (ExternalOrder.Types.SALES.toString().equalsIgnoreCase(externalOrder.getType())) {
            BonusPointsHistory history = findProcessedHistory(orderNumber, cardNumber, BonusPointsUpdatedType.INCREMENT);
            if (history == null) {
                if (customer != null) {
                    calculateBonusPoints(customer, externalOrder);
                    Long transactionId = incrementBonusPoints(customer,
                            externalOrder.getOrderNumber(),
                            BigInteger.valueOf(externalOrder.getTotalBonusPoints()),
                            BonusPointsSource.OFFLINE_STORE,
                            BonusPointsUpdatedDetail.ORDER,
                            String.format("线下订单:%s,赠送积分%d", externalOrder.getOrderNumber(),
                                    externalOrder.getTotalBonusPoints()),
                            JSONObject.toJSONString(externalOrder));
                    return transactionId;
                } else {
                    if (customer == null) {
                        throw new ExternalOrderProcessException(String.format("在门店%s未找会员卡%s的绑定记录", store.getExternalId(), cardNumber));
                    }
                    /**
                     * 理论上不会执行这之后的程序
                     * **/
                    //当前会员未绑定该会员卡,查询绑卡记录
                    CustomerCardHistory cardHistory = customerCardHistoryRepository.findFirstByMerchantIdAndCardNumber(store.getMerchantId(), cardNumber);
                    if (cardHistory == null) {
                        cardHistory = new CustomerCardHistory();
                        cardHistory.setCardNumber(cardNumber);
                        cardHistory.setOpenid(null);
                        cardHistory.setMerchantId(store.getMerchantId());
                        cardHistory.setUnionId(null);
                        cardHistory.setTelephone(null);
                        cardHistory = customerCardHistoryRepository.save(cardHistory);
                    }
                    //未找到对应的用户
                    calculateBonusPoints(null, externalOrder);
                    int updated = customerCardHistoryRepository.incrementHistoryBonusPoints(cardHistory.getId(), BigInteger.valueOf(externalOrder.getTotalBonusPoints()));
                    if (updated == 0) {
                        throw new ExternalOrderProcessException(String.format("订单%s积分失败，会员卡%s", externalOrder.getOrderNumber(), cardNumber));
                    } else {
                        BonusPointsHistory bonusPointsHistory = saveBonusPointsHistory(store,
                                cardNumber,
                                externalOrder.getOrderNumber(),
                                externalOrder.getOriginNumber(),
                                BigInteger.valueOf(externalOrder.getTotalBonusPoints()),
                                BonusPointsUpdatedType.INCREMENT,
                                BonusPointsUpdatedDetail.ORDER,
                                BonusPointsSource.OFFLINE_STORE,
                                String.format("线下订单:%s,赠送积分%d", externalOrder.getOrderNumber(), externalOrder.getTotalBonusPoints()),
                                JSONObject.toJSONString(externalOrder));
                        return bonusPointsHistory.getTransactionId();
                    }
                }
            } else {
                return history.getTransactionId();
            }
        } else {
            throw new RuntimeException(String.format("订单类型异常：%s", externalOrder.getType()));
        }
    }

    /**
     * 计算会员外部订单积分
     *
     * @param user
     * @param externalOrder
     * @throws OriginOrderNotFundException
     */
    private void calculateBonusPoints(Customer user, ExternalOrder externalOrder) throws OriginOrderNotFundException {
        //Assert.notNull(user, "会员不能为空");
        Assert.notNull(externalOrder, "订单不能为空");
        List<ExternalOrderItem> items = externalOrder.getItems();
        if (CollectionUtils.isEmpty(items)) {
            throw new RuntimeException("订单行项目不能为空");
        }
        final Integer[] totalBonusPoints = {0};
        if (ExternalOrder.Types.REFUND.toString().equalsIgnoreCase(externalOrder.getType())) {
            Assert.notNull(externalOrder.getOriginNumber(), "原单不能为空");
            //获取外部订单的积分历史（增加）
            BonusPointsHistory history = bonusPointsHistoryRepository.findFirstByNumberAndCardNumberAndUpdatedType(externalOrder.getOriginNumber(),
                    externalOrder.getCardNumber(),
                    BonusPointsUpdatedType.INCREMENT);
            //没有积分历史，表明没有需要退的积分，所以直接返回
            if (null == history) {
                throw new OriginOrderNotFundException(String.format("未找到会员:%s的相关原单记录信息：%s", externalOrder.getCardNumber(), externalOrder.getOriginNumber()));
            }
            //获取原始订单
            ExternalOrder originExternalOrder = JSON.parseObject(history.getData(), ExternalOrder.class);
            //根据原单计算订单需要减少的积分
            calculateBonusPointsByOrigin(originExternalOrder, externalOrder);
        } else if (ExternalOrder.Types.SALES.toString().equalsIgnoreCase(externalOrder.getType())) {
            items.forEach(externalOrderItem -> {
                String itemCode = externalOrderItem.getItemCode();
                String catcode = externalOrderItem.getCategoryCode();
                Map ratioMap = null;
                if (user != null) {
                    ratioMap = getBonusPointsRatio(itemCode, catcode, user, externalOrderItem.isPromotionApplied());
                } else {
                    MerchantStore store = storeService.getMerchantStoreByExternalId(externalOrder.getStoreCode());
                    ratioMap = getCustomConfigMap(store, catcode, externalOrderItem.isPromotionApplied());
                }
                if (ratioMap == null) {
                    return;
                }
                Integer bonusPointsRate = (Integer) ratioMap.get("ratio");
                BigDecimal times = (BigDecimal)ratioMap.get("times");
                if (bonusPointsRate != null && bonusPointsRate.compareTo(0) > 0) {
                    //计算顺序：单个商品价格乘以积分倍率除以积分率得到单商品的积分值最后乘以数量得到行项目的积分值
                    Integer points = externalOrderItem.getSalesPrice().multiply(times == null ? BigDecimal.ONE : times).divide(BigDecimal.valueOf(bonusPointsRate), 2, BigDecimal.ROUND_HALF_UP).multiply(externalOrderItem.getQuantity()).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
                    externalOrderItem.setBonusPoints(points);
                    totalBonusPoints[0] += points;
                }
            });
            externalOrder.setTotalBonusPoints(totalBonusPoints[0]);
        } else {
            throw new RuntimeException(String.format("订单类型异常：%s", externalOrder.getType()));
        }

    }

    private void calculateBonusPointsByOrigin(ExternalOrder originExternalOrder, ExternalOrder returnOrder) {
        Assert.notNull(originExternalOrder, "原单不能为空");
        Assert.notNull(returnOrder, "订单不能为空");
        List<ExternalOrderItem> returnItems = returnOrder.getItems();
        List<ExternalOrderItem> originOrderItems = originExternalOrder.getItems();
        //校验退单行项目（1、必须包含在原单行项目里 2、行项目数量不能大于原单行项目数量)
        checkValid(returnItems, originOrderItems);
        //获取原单单个单项目单位数量积分Map
        Map<String, Integer> originItemBonusMap = getOriginItemBonus(originOrderItems);
        Integer decrementBonusValue = 0;
        if (CollectionUtils.isNotEmpty(returnItems)) {
            for (ExternalOrderItem item : returnItems) {
                //获取原单行项目单位数量积分
                Integer originItemBonus = originItemBonusMap.get(item.getItemCode());
                String sumKey = item.getItemCode() + "SUM";
                //四舍五入得到当前行项目需要减少的积分值
                Integer value = item.getQuantity().multiply(BigDecimal.valueOf(originItemBonus)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
                Integer sum = originItemBonusMap.get(sumKey);
                //设置退货订单行项目的积分
                Integer itemBonus = value <= sum ? value : sum;
                //TODO 多次退货计算总积分不能大于原单积分
                item.setBonusPoints(itemBonus);
                decrementBonusValue += itemBonus;
            }
            returnOrder.setTotalBonusPoints(decrementBonusValue);
        }
    }

    /**
     * 获取原单行项目单位数量的积分率
     *
     * @param originOrderItems
     * @return
     */
    private Map<String, Integer> getOriginItemBonus(List<ExternalOrderItem> originOrderItems) {
        Map<String, Integer> map = new HashMap<>(originOrderItems.size() * 2);
        for (ExternalOrderItem item : originOrderItems) {
            String sumKey = item.getItemCode() + "SUM";
            if (!map.containsKey(sumKey)) {
                //该商品行项目的总积分
                map.put(sumKey, item.getBonusPoints());
            } else {
                map.put(sumKey, map.get(sumKey) + item.getBonusPoints());
            }
            //该商品单位数量的积分率
            map.put(item.getItemCode(), BigDecimal.valueOf(item.getBonusPoints()).divide(item.getQuantity(), 0, BigDecimal.ROUND_HALF_UP).intValue());
        }
        return map;
    }

    /**
     * 四舍五入，获取整型值
     *
     * @param value
     * @return
     */
    private Integer roundUpHalf(BigDecimal value) {
        Integer result = value.intValue();
        Integer value1 = BigDecimalUtil.minus(value.doubleValue(), new BigDecimal("10").doubleValue()).intValue();
        if (value1 % 10 >= 5) {
            result += 1;
        }
        return result;
    }

    /**
     * 获取原单单个行项目quantity=1的积分
     *
     * @param originOrderItems
     * @return
     */
    private Map<String, BigDecimal> getUnitBonus(List<ExternalOrderItem> originOrderItems) {
        Map<String, BigDecimal> unitBonusMap = new HashMap<>();
        for (ExternalOrderItem orderItem : originOrderItems) {
            BigDecimal unitBonus = BigDecimalUtil.divide(orderItem.getBonusPoints().doubleValue(), orderItem.getQuantity().doubleValue());
            unitBonusMap.put(orderItem.getItemCode(), unitBonus);
        }
        return unitBonusMap;
    }

    /**
     * /校验退货条目是否包含在原单条目中，并且数量必须小于原单条目
     *
     * @param returnItems      退当行项目
     * @param originOrderItems 原单行项目
     */
    private void checkValid(List<ExternalOrderItem> returnItems, List<ExternalOrderItem> originOrderItems) {
        //TODO
        Map<String, BigDecimal> map = new HashMap<>();

        for (ExternalOrderItem originItem : originOrderItems) {
            if (map.containsKey(originItem.getItemCode())) {
                map.put(originItem.getItemCode(), map.get(originItem.getItemCode()).add(originItem.getQuantity()));
            } else {
                map.put(originItem.getItemCode(), originItem.getQuantity());
            }
        }
        //合并外部订单行项目
        integrateReturnItems(returnItems);
        for (ExternalOrderItem returnItem : returnItems) {
            BigDecimal quantity = map.get(returnItem.getItemCode());
            if (null == quantity) {
                throw new ExternalOrderProcessException("原单中不存在该退货行项目：" + returnItem.getItemCode());
            }
            if (returnItem.getQuantity().compareTo(quantity) > 0) {
                throw new ExternalOrderProcessException("退货行项目："
                        + returnItem.getItemCode()
                        + " 数量："
                        + returnItem.getQuantity()
                        + " 大于原单行项目数量:"
                        + quantity);
            }
        }
    }

    /**
     * 整合外部订单行项目，将相同的行项目数据数目相加
     *
     * @param returnItems
     * @return
     */
    private void integrateReturnItems(List<ExternalOrderItem> returnItems) {
        Collections.sort(returnItems, new Comparator<ExternalOrderItem>() {
            @Override
            public int compare(ExternalOrderItem o1, ExternalOrderItem o2) {
                return o1.hashCode() - o2.hashCode();
            }
        });
        int i = 0;//[0,i]之间为不重复元素
        int j = i + 1;//待与i指向的元素比较的元素
        while (j < returnItems.size()) {
            ExternalOrderItem item1 = returnItems.get(i);
            ExternalOrderItem item2 = returnItems.get(j);
            if (item1.equals(item2)) {
                item1.setQuantity(item1.getQuantity().add(item2.getQuantity()));
                returnItems.remove(j);

            } else {
                i++;
                j++;
            }
        }
    }


    /**
     * 是否已经处理过积分
     *
     * @param orderNumber
     * @param loyaltyNo
     * @param type
     * @return
     */
    @Override
    public boolean hasProcessed(String orderNumber, String loyaltyNo, BonusPointsUpdatedType type) {
        BonusPointsHistory history = findProcessedHistory(orderNumber, loyaltyNo, type);
        return history != null;
    }

    /**
     * 查询对应的历史记录
     *
     * @param orderNumber
     * @param loyaltyNo
     * @param type
     * @return
     */
    @Override
    public BonusPointsHistory findProcessedHistory(String orderNumber, String loyaltyNo, BonusPointsUpdatedType type) {
        Assert.hasText(orderNumber, "订单编号不能为空");
        Assert.hasText(loyaltyNo, "会员卡号不能为空");
        Assert.notNull(type, "更新类型不能为空");
        BonusPointsHistory history = bonusPointsHistoryRepository.findFirstByNumberAndCardNumberAndUpdatedType(orderNumber, loyaltyNo, type);
        return history;
    }

    /**
     * @param orderNumber 购物时积分的原单号
     * @param storeCode   门店号
     * @return
     * @throws OriginOrderNotFundException
     */
    @Override
    public Integer check(String orderNumber, String storeCode) throws OriginOrderNotFundException {
        Assert.hasText(orderNumber, "订单号不能为空");
        Assert.hasText(storeCode, "门店编号不能为空");
        //查询原单的积分历史
        BonusPointsHistory history = bonusPointsHistoryRepository.findFirstByNumberAndUpdatedType(orderNumber, BonusPointsUpdatedType.INCREMENT);
        if (null == history) {
            throw new OriginOrderNotFundException(String.format("未找到原单记录信息：%s", orderNumber));
        }
        //获取需要减少的积分
        BigInteger decrementValue = history.getUpdatedValue();
        //获取会员卡号
        String cardNumber = history.getCardNumber();
        MerchantStore store = storeService.getMerchantStoreByExternalId(storeCode);
        if (store == null) {
            throw new ExternalOrderProcessException(String.format("对应编号%s的门店在系统中为注册", storeCode));
        }
        Customer customer = customerRepository.findFirstByMerchantStoreAndLoyaltyCard(store, cardNumber);
        if (customer != null) {
            //为当前绑定会员
            BigInteger bonusPoints = customer.getBonusPoints();
            return (bonusPoints == null ? BigInteger.ZERO : bonusPoints).subtract(decrementValue).intValue();
        } else {
            CustomerCardHistory cardHistory = customerCardHistoryRepository.findFirstByMerchantIdAndCardNumber(store.getMerchantId(), cardNumber);
            if (null == cardHistory) {
                throw new OriginOrderNotFundException(String.format("未找到原单记录信息：%s", orderNumber));
            } else {
                return cardHistory.getBonusPonits().subtract(decrementValue).intValue();
            }
        }
    }

    /**
     * 返回积分历史
     *
     * @param cardNumber
     * @param merchantId
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public Page<BonusPointsHistory> getBonusPointsHistories(String cardNumber, Long merchantId, Integer page, Integer pageSize) {
        Pageable pageable = new PageRequest(page - 1, pageSize, Sort.Direction.DESC, "updatedTimestamp");
        Specification<BonusPointsHistory> specification = new Specification<BonusPointsHistory>() {
            @Override
            public Predicate toPredicate(Root<BonusPointsHistory> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                predicates.add(cb.equal(root.get("cardNumber"), cardNumber));
                predicates.add(cb.equal(root.get("merchantId"), merchantId));
                Predicate[] p = new Predicate[predicates.size()];
                return cb.and(predicates.toArray(p));
            }
        };
        return bonusPointsHistoryRepository.findAll(specification, pageable);
    }

    /**
     * 重新处理异常订单
     *
     * @param exeOrdeId
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Long processExceptionOrder(Long exeOrdeId) throws BonusPointsNotEnoughException, OriginOrderNotFundException {
        Assert.notNull(exeOrdeId, "异常订单id不能为空");
        BonusExceptionOrder exceptionOrder = exceptionOrderRepository.findOne(exeOrdeId);
        if (exceptionOrder != null) {
            String data = exceptionOrder.getData();
            if (data != null) {
                ExternalOrder externalOrder = JSONObject.parseObject(data, ExternalOrder.class);
                Long transactionId = processExternalOrder(externalOrder);
                exceptionOrderRepository.delete(exeOrdeId);
                return transactionId;
            }
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addBonusPoints(Customer user, String addPointsValue, String remark, BonusPointsUpdatedDetail type) {
        BigInteger bonusPoint = user.getBonusPoints();
        BigInteger afterValue = bonusPoint.add(new BigInteger(addPointsValue));
        int update = customerRepository.saveWithOriginalBonusPoints(BigInteger.valueOf(Long.valueOf(addPointsValue)),bonusPoint,user.getCustomerId());
        if(update != 1){
            log.error("获取积分失败，失败原因：积分发生变化");
            throw new RuntimeException("积分失败，请重新尝试");
        }
        user = customerRepository.findOne(user.getCustomerId());
        BonusPointsHistory bonusPointsHistory = createBonusPointsHistory(addPointsValue,afterValue,user,remark,type);
        insertBonusPointsHistory(bonusPointsHistory);

    }

    /**
     * 增加会员积分变动日志
     * @param bonusPointsHistory：积分变动记录
     * @return 积分变动记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public BonusPointsHistory insertBonusPointsHistory(BonusPointsHistory bonusPointsHistory) {

        return bonusPointsHistoryRepository.save(bonusPointsHistory);
    }

    @Override
    public PageVO findByPage(String startTime, String endTime,String name,List<Long> merchantStoreIdList, String cardNum, String number, Integer pageCode, Integer pageSize) {
        Pageable pageable = new PageRequest(pageCode-1,pageSize,new Sort(Sort.Direction.DESC,"bonusPointsHistory"));
        Specification<BonusPointsHistory> specification = new Specification<BonusPointsHistory>() {
            @Override
            public Predicate toPredicate(Root<BonusPointsHistory> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>();
                if(StringUtils.isNotBlank(name)){
                    System.err.println(merchantStoreIdList.get(0));
                    List<Long> customerIds = customerRepository.findNameLikeAndStoreId(name,merchantStoreIdList.get(0));

                    if(CollectionUtils.isNotEmpty(customerIds)){

                        Path<String> queryCardNum = root.get("cardNumber");

                        predicates.add(queryCardNum.in(customerIds));
                    }
                }
                if(!StringUtils.isBlank(startTime)){
                    Path<Date> queryStartCreateTime = root.get("updatedTimestamp");
                    Date startDate = DateUtils.parse(startTime, DateUtils.DEFAULT_DATE_FORMAT);
                    predicates.add(cb.greaterThanOrEqualTo(queryStartCreateTime,startDate));
                }
                if(!StringUtils.isBlank(endTime)){
                    Path<Date> queryEndCreateTime = root.get("updatedTimestamp");
                    Date endData = DateUtils.parse(endTime, DateUtils.DEFAULT_DATE_FORMAT);
                    predicates.add(cb.lessThanOrEqualTo(queryEndCreateTime,endData));
                }
                if(!StringUtils.isBlank(cardNum)){
                    //增加查询条件：会员卡号
                    predicates.add(cb.equal(root.get("cardNumber"),cardNum.trim()));
                }
                if(CollectionUtils.isNotEmpty(merchantStoreIdList)){
                    //增加查询条件：门店
                    CriteriaBuilder.In<Long> in = cb.in(root.get("merchantId"));
                    for(Long id : merchantStoreIdList){
                        in.value(id);
                    }
                    predicates.add(in);
                }
                if(StringUtils.isNotBlank(number)){
                    predicates.add(cb.like(root.get("number"),"%"+number.trim()+"%"));
                }
                Predicate[] p = new Predicate[predicates.size()];
                return cb.and(predicates.toArray(p));
            }
        };
        Page<BonusPointsHistory> page = bonusPointsHistoryRepository.findAll(specification, pageable);
        //判断有无数据
        if(CollectionUtils.isEmpty(page.getContent())){
            return null;
        }
        List<BonusPointsHistoryVo> bonusPointsHistoryVOList = Lists.newArrayList();
        for(BonusPointsHistory bonusPointsHistory : page.getContent()){
            BonusPointsHistoryVo bonusPointsHistoryVO = new BonusPointsHistoryVo();
 /*           String orderNum = bonusPointsHistory.getNumber();
            if(orderNum.equals("0000000000000")){
                bonusPointsHistoryVO.setDescription("门店管理员修改");
            }
            bonusPointsHistoryVO.setDescription(orderRepository.findDescByNum(orderNum));*/
            bonusPointsHistoryVO.setCreateBy(bonusPointsHistory.getCreateBy());
            System.err.println(bonusPointsHistory.getCreateBy());
            BeanUtils.copyProperties(bonusPointsHistory,bonusPointsHistoryVO);
            if(BonusPointsUpdatedType.DECREMENT.equals(bonusPointsHistory.getUpdatedType())){
                //当为扣减积分时，分数取反
                bonusPointsHistoryVO.setUpdatedValue(bonusPointsHistory.getUpdatedValue().negate());
            }
            if(bonusPointsHistory.getMerchantId() != null) {
                //获取门店
                MerchantStore merchantStore = merchantStoreRepository.findOne(bonusPointsHistory.getMerchantId());
                if (merchantStore != null) {
                    //组装门店信息
                    bonusPointsHistoryVO.setMerchantMessage("[" + merchantStore.getExternalId() + "]" + merchantStore.getStoreName());
                    //组装用户信息
                    Customer customer = customerRepository.findCustomerByCard(merchantStore.getMerchantId(), bonusPointsHistory.getCardNumber());
                    if(customer!=null){
                        if(customer!=null){
                            bonusPointsHistoryVO.setName(customer.getCustomerLastname());
                            bonusPointsHistoryVO.setPhone(customer.getCustomerPhone());
                        }
                    }
                }
            }

            bonusPointsHistoryVOList.add(bonusPointsHistoryVO);
        }
        Long totalElement = page.getTotalElements();
        PageVO pageVO = new PageVO(pageCode,pageSize,totalElement.intValue(),bonusPointsHistoryVOList);
        return pageVO;
    }

    @Override
    public Integer checkReturn(Long returnId) throws OriginOrderNotFundException {
        OrderReturn orderReturn = returnRepository.findOne(returnId);
        Order order = orderReturn.getRelatedOrder();
        List<OrderItem> orderItems = order.getOrderItems();
        int sumQuantity = orderItems.stream().mapToInt(OrderItem::getSkuQuantity).sum();

        // 整单退
        if(sumQuantity == orderReturn.getSkuQuantity()){
            return check(order.getOrderNumber(), order.getMerchantStore().getStoreCode());
        }
        // 退单品 -每个退单一个退单商品
        List<OrderReturnItem> returnItems = orderReturn.getOrderReturnItems();
        if( 1 != returnItems.size()){
                throw new KnownRuntimeException("未知的退单项数量");
        }
        OrderReturnItem returnItem = returnItems.get(0);
        // 找到退单项 对应订单项 比较退货数量
        Optional<OrderItem> item = orderItems.stream().filter(orderItem -> {
            return OrderItemStatus.RETURNING.equals(orderItem.getStatus())
                && returnItem.getSkuId().equals(orderItem.getSkuId());
        }).findFirst();

        OrderItem orderItem = item.get();
        if (!(item.isPresent() && 0 == (orderItem.getReturnQuantity().compareTo(returnItem.getSkuQuantity())))){
            throw new KnownRuntimeException("订单项sku不存在或退单数量不匹配");
        }
        BigInteger bonusPoints = order.getCustomer().getBonusPoints();
        Integer decrementValue = orderItem.getBonusPoints();
        return (bonusPoints==null?BigInteger.ZERO:bonusPoints).subtract(BigInteger.valueOf(decrementValue.longValue())).intValue();
    }

    @Autowired
    MerchantStoreRepository merchantStoreRepository;

    /**
     * 组装签到/推荐他人注册/注册时积分增加变更信息
     * @param addPointsValue:增加的积分数
     * @param afterValue：增加过后的积分数
     * @param user：积分增加用户
     * @param remark：积分增加备注
     * @param type：积分增加类型
     * @return 积分增加记录
     */
    private BonusPointsHistory createBonusPointsHistory(String addPointsValue, BigInteger afterValue, Customer user, String remark, BonusPointsUpdatedDetail type) {
        BonusPointsHistory bonusPointsHistory = new BonusPointsHistory();
        bonusPointsHistory.setCustomerId(user.getCustomerId());
        bonusPointsHistory.setCustomerPhone(user.getCustomerPhone());
        bonusPointsHistory.setCustomerLastName(user.getCustomerLastname());
        bonusPointsHistory.setCardNumber(user.getLoyaltyCard());
        bonusPointsHistory.setUpdatedTimestamp(new Date());
        bonusPointsHistory.setMerchantId(user.getMerchantStore() == null ? null : user.getMerchantStore().getMerchantId());
        bonusPointsHistory.setUpdatedValue(new BigInteger(addPointsValue));
        bonusPointsHistory.setUpdatedType(BonusPointsUpdatedType.INCREMENT);
        bonusPointsHistory.setTransactionId(defaultUidGenerator.getUID());
        bonusPointsHistory.setAfterValue(afterValue);
        bonusPointsHistory.setUpdatedTypeDetail(type);
        bonusPointsHistory.setStoreCode(user.getMerchantStore() == null ? null : user.getMerchantStore().getExternalId());
        bonusPointsHistory.setSource(BonusPointsSource.MALL);
        bonusPointsHistory.setRemark(remark);
        bonusPointsHistory.setNumber(defaultUidGenerator.getUID()+"");
        return bonusPointsHistory;
    }


}
